package com.eqxiu.ieditor.config;

import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager;
import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterParamFlowRuleManager;
import com.alibaba.csp.sentinel.datasource.Converter;
import com.alibaba.csp.sentinel.datasource.nacos.NacosDataSource;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRuleManager;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleManager;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.io.IOException;
import java.util.*;

/**
 * @ClassName SentinelConfig
 * @Description
 * @Author duanhuazhen
 * @Date 18:01 2020-04-15
 * @Version 1.0
 **/
@Configuration
public class SentinelConfig {

    private static final String CLUSTER_FLOW_DATASOURCE_NAME = "cluster-flow-sentinel-nacos-datasource";
    private static final String FLOW_DATASOURCE_NAME = "flow-sentinel-nacos-datasource";
    private static final String DEGRADE_DATASOURCE_NAME = "degrade-sentinel-nacos-datasource";
    private static final String AUTH_DATASOURCE_NAME = "authority-sentinel-nacos-datasource";
    private static final String SYSTEM_DATASOURCE_NAME = "system-sentinel-nacos-datasource";
    private static final String CLUSTER_PARAM_DATASOURCE_NAME = "cluster-param-sentinel-nacos-datasource";
    private static final String PARAM_DATASOURCE_NAME = "param-sentinel-nacos-datasource";

    @Value("${spring.application.name}")
    private String nameSpace;

    @Value("${sentinel.nacos.server-addr}")
    private String serverAddr;
    @Value("${sentinel.nacos.group-id}")
    private String groupId;
    @Value("${sentinel.nacos.flow.data-id}")
    private String flowDataId;
    @Value("${sentinel.nacos.degrade.data-id}")
    private String degradeDataId;
    @Value("${sentinel.nacos.authority.data-id}")
    private String authorityDataId;
    @Value("${sentinel.nacos.param.data-id}")
    private String paramDataId;

    /**
     * 单机流控规则
     */
    @Bean(name = FLOW_DATASOURCE_NAME)
    public NacosDataSource<List<FlowRule>> flowDataSource() {
        NacosDataSource<List<FlowRule>> dataSource = new NacosDataSource<>(serverAddr, groupId, flowDataId, source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {
        }));
        FlowRuleManager.register2Property(dataSource.getProperty());
        return dataSource;
    }

    /**
     * 集群流控规则
     */
    @Bean(name = CLUSTER_FLOW_DATASOURCE_NAME)
    public NacosDataSource<List<FlowRule>> clusterFlowDataSource() {
        NacosDataSource<List<FlowRule>> dataSource = new NacosDataSource<>(serverAddr, groupId, flowDataId, source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {
        }));
        ClusterFlowRuleManager.setPropertySupplier(namespace -> {
            return dataSource.getProperty();
        });
        ClusterFlowRuleManager.register2Property(nameSpace);
        return dataSource;
    }

    /**
     * 降级规则
     */
    @Bean(name = DEGRADE_DATASOURCE_NAME)
    public NacosDataSource<List<DegradeRule>> degradeDataSource() {
        NacosDataSource<List<DegradeRule>> dataSource = new NacosDataSource<>(serverAddr, groupId, degradeDataId, source -> JSON.parseObject(source, new TypeReference<List<DegradeRule>>() {
        }));
        DegradeRuleManager.register2Property(dataSource.getProperty());
        return dataSource;
    }

    /**
     * 系统规则
     */
    @Bean(name = SYSTEM_DATASOURCE_NAME)
    public NacosDataSource<List<DegradeRule>> systemDataSource() {
        NacosDataSource<List<DegradeRule>> dataSource = new NacosDataSource<>(serverAddr, groupId, degradeDataId, source -> JSON.parseObject(source, new TypeReference<List<DegradeRule>>() {
        }));
        DegradeRuleManager.register2Property(dataSource.getProperty());
        return dataSource;
    }

    /**
     * 授权规则
     */
    @Bean(name = AUTH_DATASOURCE_NAME)
    public NacosDataSource<List<AuthorityRule>> authDataSource() {
        NacosDataSource<List<AuthorityRule>> dataSource = new NacosDataSource(serverAddr, groupId, authorityDataId, new JsonConverter(AuthorityRule.class));
        AuthorityRuleManager.register2Property(dataSource.getProperty());
        return dataSource;
    }

    /**
     * 单机热点规则
     */
    @Bean(name = PARAM_DATASOURCE_NAME)
    public NacosDataSource<List<ParamFlowRule>> paramDataSource() {
        NacosDataSource<List<ParamFlowRule>> dataSource = new NacosDataSource(serverAddr, groupId, paramDataId, new JsonConverter(ParamFlowRule.class));
        ParamFlowRuleManager.register2Property(dataSource.getProperty());
        return dataSource;
    }

    /**
     * 集群热点规则
     */
    @Bean(name = CLUSTER_PARAM_DATASOURCE_NAME)
    public NacosDataSource<List<ParamFlowRule>> clusterParamDataSource() {
        NacosDataSource<List<ParamFlowRule>> dataSource = new NacosDataSource(serverAddr, groupId, paramDataId, new JsonConverter(ParamFlowRule.class));
        ClusterParamFlowRuleManager.setPropertySupplier(namespace -> {
            return dataSource.getProperty();
        });
        ClusterParamFlowRuleManager.register2Property(nameSpace);
        return dataSource;
    }


    public class JsonConverter implements Converter<String, Collection<Object>> {

        private Class clazz;

        public JsonConverter(Class clazz) {
            this.clazz = clazz;
        }

        @Override
        public Collection<Object> convert(String source) {
            if (StringUtils.isEmpty(source)) {
                return null;
            }
            ObjectMapper objectMapper = new ObjectMapper();
            Collection<Object> ruleCollection = new ArrayList<>();
            try {
                List<HashMap> sourceArray = objectMapper.readValue(source,
                        new com.fasterxml.jackson.core.type.TypeReference<List<HashMap>>() {
                        });

                for (HashMap obj : sourceArray) {
                    String item = null;
                    try {
                        item = objectMapper.writeValueAsString(obj.get("rule"));
                        Optional.ofNullable(convertRule(item, objectMapper, clazz))
                                .ifPresent(convertRule -> ruleCollection.add(convertRule));
                    } catch (IOException e) {
//                        log.error("sentinel rule convert error: " + e.getMessage(), e);
                        throw new IllegalArgumentException(
                                "sentinel rule convert error: " + e.getMessage(), e);
                    }
                }
                return ruleCollection;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }

        private Object convertRule(String ruleStr, ObjectMapper objectMapper, Class ruleClass) throws IOException {
            return objectMapper.readValue(ruleStr, ruleClass);
        }
    }

}
